<?php

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

namespace EasyActionPHP\Controller;

use EasyActionPHP\Core as Core;
use EasyActionPHP\Dispatcher as Dispatcher;
use EasyActionPHP\Util\ActionUtil as ActionUtil;
use EasyActionPHP\Exception as Except;

/**
 * ActionController.
 *
 * @final
 * @author Flavio de Vasconcellos Correa
 * @since 2007/06/04
 * @package EasyActionPHP
 * @subpackage Controller
 * @version $id$
 * @license http://www.gnu.org/copyleft/gpl.html GPL
 */
final class ActionController {

    /**
     * Responsável por armazenar toda informação de controle e configuração.
     * @var type Ar;ray
     */
    static $controllerConfig = array();

    const WELCOME_ACTION_TAG = 'welcome-action';
    const INTERCEPTORS_TAG = 'interceptors';
    const INTERCEPTOR_TAG = 'interceptor';
    const INTERCEPTOR_REF_TAG = 'interceptor-ref';
    const ACTION_INTERCEPTORS_TAG = 'action-interceptors';
    const FORM_BEANS_TAG = 'form-beans';
    const FORM_BEAN_TAG = 'form-bean';
    const GLOBAL_FORWARDS_TAG = 'global-forwards';
    const FORWARD_TAG = 'forward';
    const ACTION_MAPPINGS_TAG = 'action-mappings';
    const ACTION_TAG = 'action';
    const NAME_ATTR = 'name';
    const CLASS_ATTR = 'class';
    const PATTERN_ATTR = 'pattern';
    const TYPE_ATTR = 'type';
    const PATH_ATTR = 'path';
    const FORWARD_ATTR = 'forward';
    const REDIRECT_ATTR = 'redirect';
    const METHOD_ATTR = 'method';
    const FORM_ATTR = 'form';
    const SCOPE_ATTR = 'scope';
    const INPUT_ATTR = 'input';
    const VALIDATE_ATTR = 'validate';

    public static function addXMLControllerFile($filename) {
        if (file_exists($filename)) {
            $xml = simplexml_load_file($filename);
            if (!$xml) {
                throw new Exception('ERROR: ' . $filename . ' é um arquivo XML inválido!');
            }
            if (isset($xml->{self::WELCOME_ACTION_TAG})) {
                self::$controllerConfig[self::WELCOME_ACTION_TAG] = (string) $xml->{self::WELCOME_ACTION_TAG};
            }
            foreach ($xml->{self::INTERCEPTORS_TAG}->{self::INTERCEPTOR_TAG} as $interceptorElement) {
                $interceptorAttributes = $interceptorElement->attributes();
                self::$controllerConfig
                        [self::INTERCEPTORS_TAG]
                        [self::INTERCEPTOR_TAG]
                        [(string) $interceptorAttributes->{self::NAME_ATTR}]
                        = (string) $interceptorAttributes->{self::CLASS_ATTR};
            }
            foreach ($xml->{self::ACTION_INTERCEPTORS_TAG}->{self::INTERCEPTOR_TAG} as $interceptorElement) {
                $interceptorAttributes = $interceptorElement->attributes();
                self::$controllerConfig
                        [self::ACTION_INTERCEPTORS_TAG]
                        [self::INTERCEPTOR_TAG]
                        [self::PATTERN_ATTR]
                        [(string) $interceptorAttributes->{self::PATTERN_ATTR}]
                        = array();
                foreach ($interceptorElement->{self::INTERCEPTOR_REF_TAG} as $interceptorRefElement) {
                    $interceptorRefAttributes = $interceptorRefElement->attributes();
                    self::$controllerConfig
                            [self::ACTION_INTERCEPTORS_TAG]
                            [self::INTERCEPTOR_TAG]
                            [self::PATTERN_ATTR]
                            [(string) $interceptorAttributes->{self::PATTERN_ATTR}]
                            [self::INTERCEPTOR_REF_TAG]
                            [(string) self::NAME_ATTR] = (string) $interceptorRefAttributes->{self::NAME_ATTR};
                }
            }
            foreach ($xml->{self::FORM_BEANS_TAG} as $formBeanElement) {
                foreach ($formBeanElement->{self::FORM_BEAN_TAG} as $formBeanElement) {
                    $formBeanAttribute = $formBeanElement->attributes();
                    self::$controllerConfig
                            [self::FORM_BEANS_TAG]
                            [self::FORM_BEAN_TAG]
                            [(string) $formBeanAttribute->{self::NAME_ATTR}] = (string) $formBeanAttribute->{self::TYPE_ATTR};
                }
            }
            foreach ($xml->{self::GLOBAL_FORWARDS_TAG} as $globalForwardElement) {
                foreach ($globalForwardElement->{self::FORWARD_TAG} as $forwardElement) {
                    $forwardElementAttributes = $forwardElement->attributes();
                    self::$controllerConfig
                            [self::GLOBAL_FORWARDS_TAG]
                            [self::FORWARD_TAG]
                            [(string) $forwardElementAttributes->{self::NAME_ATTR}]
                            = array(
                        self::PATH_ATTR => (string) $forwardElementAttributes->{self::PATH_ATTR}
                        , self::REDIRECT_ATTR => (strtolower((string) $forwardElementAttributes->{self::REDIRECT_ATTR}) == 'true' ? TRUE : FALSE)
                    );
                }
            }
            foreach ($xml->{self::ACTION_MAPPINGS_TAG} as $actionMappingElement) {
                foreach ($actionMappingElement->{self::ACTION_TAG} as $actionElement) {
                    $actionElementAttributes = $actionElement->attributes();
                    $actionElementAttributesArray = array(
                        self::NAME_ATTR => (string) $actionElementAttributes->{self::NAME_ATTR}
                        , self::TYPE_ATTR => (string) $actionElementAttributes->{self::TYPE_ATTR}
                        , self::SCOPE_ATTR => (string) $actionElementAttributes->{self::SCOPE_ATTR}
                        , self::INPUT_ATTR => (string) $actionElementAttributes->{self::INPUT_ATTR}
                        , self::VALIDATE_ATTR => (strtolower((string) $actionElementAttributes->{self::VALIDATE_ATTR})) == 'true'
                        , self::FORM_ATTR => (string) $actionElementAttributes->{self::FORM_ATTR}
                        , self::METHOD_ATTR => (string) $actionElementAttributes->{self::METHOD_ATTR}
                        , self::FORWARD_ATTR => (string) $actionElementAttributes->{self::FORWARD_ATTR}
                        , self::REDIRECT_ATTR => (strtolower((string) $actionElementAttributes->{self::REDIRECT_ATTR})) == 'true'
                    );
                    self::$controllerConfig
                            [self::ACTION_MAPPINGS_TAG]
                            [self::ACTION_TAG]
                            [(string) $actionElementAttributes->{self::NAME_ATTR}]
                            = $actionElementAttributesArray;
                    foreach ($actionElement->{self::FORWARD_TAG} as $forwardElement) {
                        $forwardElementAttributes = $forwardElement->attributes();
                        self::$controllerConfig
                                [self::ACTION_MAPPINGS_TAG]
                                [self::ACTION_TAG]
                                [(string) $actionElementAttributes->{self::NAME_ATTR}]
                                [self::FORWARD_TAG]
                                [(string) $forwardElementAttributes->{self::NAME_ATTR}] = array
                            (
                            self::PATH_ATTR => (string) $forwardElementAttributes->{self::PATH_ATTR}
                            , self::REDIRECT_ATTR => (strtolower((string) $forwardElementAttributes->{self::REDIRECT_ATTR}) == 'true')
                        );
                    }
                }
            }
        } else {
            throw new \Exception('ERROR: ' . $filename . ' not found!');
        }
    }

    /**
     * Executa uma a&ccedil;&atilde;o.
     *
     * @access public
     * @return void
     */
    public static function execute() {
        try {

            $httpResponse = HttpResponse::getInstance();
            $httpRequest = HttpRequest::getInstance();

            $do = $httpRequest->getAttribute(Core\Application::getParamByName('__ACTION_PARAM'));

            if (ActionUtil::emptyOrNull($do)) {
                if (ActionUtil::emptyOrNull((self::$controllerConfig[self::WELCOME_ACTION_TAG]))) {
                    throw new \Exception('ERROR: \'action\' not defined and \'welcome-action\' not declared');
                }
                $do = self::$controllerConfig[self::WELCOME_ACTION_TAG];
            }
            if (count(self::$controllerConfig[self::ACTION_MAPPINGS_TAG]) == 0) {
                throw new \Exception('ERROR: Não há nenhuma ação declarada!!!');
            }
            // Verifica se a chamada de ação esta na base
            $actionExists = array_key_exists($do, self::$controllerConfig[self::ACTION_MAPPINGS_TAG][self::ACTION_TAG]);
            if ($actionExists) {
                $actionTag = self::$controllerConfig[self::ACTION_MAPPINGS_TAG][self::ACTION_TAG][$do];
                if (ActionUtil::emptyOrNull($actionTag[self::TYPE_ATTR])) {
                    $actionForward = new ActionForward(ActionForward::FORWARD_SUCCESS, $actionTag[self::FORWARD_TAG], $actionTag[self::REDIRECT_ATTR]);
                    self::launchInterceptor($do, 'before');
                    $content = Dispatcher\ActionDispatcher::execute($actionForward, $httpResponse);
                    self::launchInterceptor($do, 'after');
                    return $content;
                } else {
                    $className = $actionTag[self::TYPE_ATTR];
                    if (TRUE) {
                        $actionObject = new $className();
                        $actionMethod = ActionUtil::emptyOrNull($actionTag[self::METHOD_ATTR]) ? 'execute' : $actionTag[self::METHOD_ATTR];
                        if (method_exists($actionObject, $actionMethod)) {
                            if ($actionObject instanceof AbstractAction) {
                                if ($actionObject instanceof AbstractActionForm) {
                                    $formBeanName = $actionTag[self::FORM_ATTR];
                                    if (array_key_exists($formBeanName, self::$controllerConfig[self::FORM_BEANS_TAG][self::FORM_BEAN_TAG])) {
                                        $formBeanTag = self::$controllerConfig[self::FORM_BEANS_TAG][self::FORM_BEAN_TAG];
                                        $formBeanClass = $formBeanTag[$formBeanName];
                                        $formBeanInstance = new $formBeanClass();
                                        $formBeanInstance->reset();
                                        switch ($actionTag[self::SCOPE_ATTR]) {
                                            case 'session':
                                                foreach ($httpRequest->getSession() as $k => $v) {
                                                    $httpResponse->setAttribute($k, $v);
                                                }
                                                $formBeanInstance->populate($httpRequest->getSession());
                                                break;
                                            case 'request':
                                            default:
                                                if ($httpRequest->isGet()) {
                                                    foreach ($httpRequest->getAttributes() as $k => $v) {
                                                        $httpResponse->setAttribute($k, $v);
                                                    }
                                                    $formBeanInstance->populate($httpRequest->getAttributes());
                                                } else {
                                                    foreach ($httpRequest->getProperties() as $k => $v) {
                                                        $httpResponse->setAttribute($k, $v);
                                                    }
                                                    $formBeanInstance->populate($httpRequest->getProperties());
                                                }
                                        }
                                        foreach ($formBeanInstance->getFieldValues() as $name => $value) {
                                            $httpResponse->setAttribute($name, $value);
                                        }
                                        $validate = $actionTag[self::VALIDATE_ATTR];
                                        if ($validate) {
                                            $formErrors = $formBeanInstance->validate();
                                            if ($formErrors->getCount() > 0) {
                                                $httpResponse->setAttribute('__EASYFORM_ERRORS__', $formErrors->errorList());
                                                $input = $actionTag[self::INPUT_ATTR];
                                                $forward = new ActionForward('FORM_INVALID', $input, false);
                                                self::launchInterceptor($do, 'before');
                                                self::launchInterceptor($do, 'after');
                                                $content = Dispatcher\ActionDispatcher::execute($forward, $httpResponse);
                                                return $content;
                                            }
                                        }
                                    }
                                }
                                $forwardMap = new ActionForwardMapping();
                                foreach ($actionTag[self::FORWARD_TAG] as $forwardName => $forward) {
                                    $forwardPath = $forward[self::PATH_ATTR];
                                    $forwardRedirect = $forward[self::REDIRECT_ATTR];
                                    $forwardMap->add(new ActionForward($forwardName, $forwardPath, $forwardRedirect));
                                }
                                if (count($forwardMap->getForwards()) == 0) {
                                    throw new \Exception('ERROR: The ' . $actionObject . ' not declare any Forward class');
                                }

                                self::launchInterceptor($do, 'before');
                                $actionForward = $actionObject->$actionMethod($httpRequest, $httpResponse, $forwardMap, $formBeanInstance);
                                self::launchInterceptor($do, 'after');
                                $content = Dispatcher\ActionDispatcher::execute($actionForward, $httpResponse);
                                return $content;
                            }
                        } else {
                            throw new Except\InvalidMethodException('Method '.$actionMethod.' not defined in '.$className);
                        }
                    } else {
                        throw new Except\InvalidClassException('Class ' . $className . ' not exists.');
                    }
                }
            } else {
                throw new Except\InvalidActionException('Parâmetro \'action\' inexistente ou usuário sem permissão.');
            }
        } catch (\Exception $e) {
            throw ($e);
        }
    }

// execute

    private function launchInterceptor($do, $method) {

        $actionInterceptorsArray = self::$controllerConfig
                [self::ACTION_INTERCEPTORS_TAG]
                [self::INTERCEPTOR_TAG]
                [self::PATTERN_ATTR];

        $interceptorStack = array();
        foreach ($actionInterceptorsArray as $patternKey => $actionInterceptorsRef) {
            $eregPattern = '/*' . $patternKey;
            if (!(ereg($eregPattern, $do) === FALSE)) {
                foreach ($actionInterceptorsRef as $actionInterceptorRef) {
                    $interceptorRefName = $actionInterceptorRef[self::NAME_ATTR];
                    $interceptorArray = self::$controllerConfig
                            [self::INTERCEPTORS_TAG]
                            [self::INTERCEPTOR_TAG];
                    if (array_key_exists($interceptorRefName, $interceptorArray)) {
                        $interceptorClass = $interceptorArray[$interceptorRefName];
                        $interceptorObject = new $interceptorClass();
                        if ($interceptorObject instanceof ActionInterceptor) {
                            $interceptorStack[] = $interceptorObject;
                        }
                    }
                }
            }
        }
        if (count($interceptorStack) > 0) {
            $httpResponse = HttpResponse::getInstance();
            $httpRequest = HttpRequest::getInstance();
            foreach ($interceptorStack as $interceptorObject) {
                $interceptorObject->$method($httpRequest, $httpResponse);
            }
        }
        return;
    }

// launchInterceptor

    /**
     * Retorna um objeto ActionForward no buffer de GlobalForwards.
     *
     * @access public
     * @param String name Referencia a uma instancia de ActionForward.
     * @return ActionForward
     */
    public static function getGlobalForward($name) {

        $globalForwardsArray = self::$controllerConfig[self::GLOBAL_FORWARDS_TAG][self::FORWARD_TAG];
        if (array_key_exists($name, $globalForwardsArray)) {
            $forwardTag = $globalForwardsArray[$name];
            $forward = new ActionForward($name, $forwardTag[self::PATH_ATTR], $forwardTag[self::REDIRECT_ATTR]);
            return $forward;
        } else {
            throw new Except\InvalidForwardCallException("Chamada \"$name\"inexistente.");
        }
        return null;
    }

// getGlobalForward
}

?>